SuperStrict
Framework BRL.GlMax2D
Import brl.Random


'src idea:
'http://www.niksula.hut.fi/~hkankaan/Homepages/metaballs.html
AppTitle = "fast metaballs. "
'HideMouse()
Global resX:Int=800, resY:Int=600
SetGraphicsDriver GLMax2DDriver()
Graphics resX,resY,0,0

SeedRnd(0)'MilliSecs() 

Local numBalls:Int = 10
Local balls:tball[numBalls]
For Local i:Int=0 Until numBalls
	balls[i] = TBall.Create( TPoint.Create( Rand(150,resX-150), Rand(150,resY-150) ), 1 )'Rand(1,4)
Next

'Local threshold:Double = 0.00001'0.0000001'0.003 '0.0004 'higher value -> smaller metaballs
Local threshold:Double = 1e-5
'higher goo value -- less gooey. try g=4 and g=2
Local mbs:tmetaballsystem = TMetaballsystem.Create(balls, 3, threshold) 'balls, gooeyness, and potential field threshold

Local iterations:Int = 0
Local fps:Int = 0
Local lastSec:Long = MilliSecs() + 1000
Local frames:Int = 0

Global bench1:Int = 0
Local closestBall:TBall = Null

While(Not KeyHit(KEY_ESCAPE) And Not AppTerminate()	)
	Cls

	If(MouseDown(1) And closestBall <> Null)
		
		closestBall.pos.x = MouseX()
		closestBall.pos.y = MouseY() 
		
	Else
		'Find ball closest to mouse
		Local minDist# = 1e32
		For Local b:TBall = EachIn balls
			Local distSq# = (MouseX() - b.pos.x)^2 + (MouseY() - b.pos.y)^2
			If(distSq < minDist)
				closestBall = b
				minDist = distSq
			EndIf
		Next
	EndIf
	
	Local t# = MilliSecs() / 1000.0
	
	If MouseDown(2)
		mbs.drawBalls(20) 'sim_step size, dt in pixels to move 
	Else
		'mbs.optim()
		mbs.marchingSquares()
	'	balls[1].pos.x = resX/2.0 + 60 * Cos(2*t * 180/Pi)
	'	balls[1].pos.y = resY/2.0 + 60 * Sin(2*t * 180/Pi)
	EndIf
	If(closestBall <> Null) Then DrawLine(MouseX(), MouseY(), closestBall.pos.x, closestBall.pos.y )
	
	
	SetColor 255,255,0
	DrawText("iterations: "+iterations+"    fps: "+fps, 0,0)
	'DrawText("bench1: "+(bench1/1000.0),0,15)
	bench1 = 0
	
	If(lastSec < MilliSecs() )
		lastSec = MilliSecs() + 1000
		fps = frames
		frames = 0
	EndIf
	
	iterations:+1
	frames :+ 1
	Flip
	
Wend
End

Type TPoint
	Field x:Double, y:Double
	
	Function Create:TPoint(x:Double, y:Double)
		Local tmp:TPoint = New TPoint
		tmp.x = x
		tmp.y = y
		Return tmp
	End Function
	
	Method clone:TPoint()
		Local tmp:TPoint = New TPoint
		tmp.x = x
		tmp.y = y
		Return tmp
	End Method
	
	Method set(x:Double, y:Double)
		Self.x = x
		Self.y = y
	End Method
	
	Method add(other:TPoint)
		Self.x :+ other.x; Self.y :+ other.y
	End Method
End Type

Type TLine
	Field x1:Float, y1:Float, x2:Float, y2:Float
	
	Function Create: TLine(x1:Float, y1:Float, x2:Float, y2:Float)
		Local tmp: TLine = New TLine
		tmp.x1 = x1
		tmp.y1 = y1
		tmp.x2 = x2
		tmp.y2 = y2
		Return tmp
	End Function
	
End Type

Type TBall
	
	Field pos:TPoint
	Field pos0:TPoint
	Field edgePos:tpoint
	Field size:Double
	Field tracking:Int 'Bool
	
	Field found:Int
	Field isComputed:Int
		
	Function Create:TBall(pos:tpoint, size:Double)
		Local tmp:TBall = New TBall
		tmp.pos = pos
		tmp.pos0 = pos
		tmp.edgePos = pos
		tmp.size = size
		tmp.tracking = False
		Return tmp
	End Function

End Type

Type TMetaballsystem
	Const borderStepSize:Double = 0.01'0.01 'number of pixel to move in search of border offset from center
	Field balls:tball[]
	Field goo:Double
	Field threshold:Double
	Field minSize:Double
	Field oneOverGoo:Double
	Field minOverThresPowGoo:Double
	
	Field gridSiz:Float = 1.0
	Field gridW:Int = resX / gridSiz
	Field gridH:Int = resY / gridSiz
	Field grid:Int[,] = New Int[gridW,gridH]
	
	Field vUp:TPoint = TPoint.Create(0, -1)
	Field vDown:TPoint = TPoint.Create(0, 1)		Field vLeft:TPoint = TPoint.Create(-1, 0)
	Field vRight:TPoint = TPoint.Create(1, 0)
	
	Field march:TPoint[15]
	Field variations_ccw:TPoint[15]
	
	Method New()

		march[0] = vRight 
		march[1] = vDown 
		march[2] = vLeft 
		march[3] = vLeft 
		march[4] = vRight 
		march[5] = vDown 
		march[6] = vLeft 
		march[7] = vLeft 
		march[8] = vUp 
		march[9] = vUp 
		march[10] = vUp 
		march[11] = vUp 
		march[12] = vRight 
		march[13] = vDown 
		march[14] = vRight 
		'last one is illegal
		
		variations_ccw[%0000] = vRight 'Null
		variations_ccw[%0001] = vDown
		variations_ccw[%0010] = vLeft
		variations_ccw[%0011] = vLeft
		variations_ccw[%0100] = vRight
		variations_ccw[%0101] = VDown
		variations_ccw[%0110] = vLeft
		variations_ccw[%0111] = vLeft
		variations_ccw[%1000] = vUp
		variations_ccw[%1001] = vUp
		variations_ccw[%1010] = vUp
		variations_ccw[%1011] = vUp
		variations_ccw[%1100] = vRight
		variations_ccw[%1101] = vDown
		variations_ccw[%1110] = vRight
		' last is illegal
		
	End Method
	
	Function Create:TMetaballsystem(balls:TBall[], goo:Double, threshold:Double)
		Local tmp:TMetaballsystem = New TMetaballsystem
		
		tmp.balls = balls
		tmp.goo = goo
		tmp.threshold = threshold
		tmp.minSize = 1e32 'smallest ball
		tmp.oneOverGoo = 1.0/goo
		For Local b:TBall = EachIn balls
			If b.size < tmp.minSize Then tmp.minSize = b.size
		Next
		tmp.minOverThresPowGoo = (tmp.minSize/threshold)^(tmp.oneOverGoo)
		
		Return tmp
	End Function 
	
	Method calcForce:Double(pos:tpoint) 'called by stepOnceTowardsBorder
		'return the metaball fields force at point "pos"
		Local forceAtPoint:Double = 0.0
		
		For Local ball:tball = EachIn balls
			'### Formula (1)
			Local tmp:TPoint = TPoint.Create(ball.pos.x-pos.x,ball.pos.y-pos.y)
			Local distSq:Double = ( tmp.x*tmp.x + tmp.y*tmp.y )^goo
			Local dist:Double = Sqr(distSq)
			
			'If( dist > 0.707 ) Continue Else forceAtPoint :+ dist^4 - dist^2 + 0.25
			If( dist > 1e-6 ) 'prevent div by zero
				forceAtPoint :+ (ball.size) / dist
			Else
				forceAtPoint :+ 10000.0'"big number
			EndIf
		Next
		Return forceAtPoint
	End Method
	
	Method calcNormal:tpoint(pos:tpoint) 'return a normalized (magnitude==1) tangent at points "pos"
		Local np:TPoint=TPoint.Create(0,0)
		
		For Local ball:tball = EachIn balls
			' ### Formula (3)
			Local fromPointToBall:TPoint = TPoint.Create(ball.pos.x-pos.x, ball.pos.y-pos.y)
			Local centerDist:Double= Sqr(fromPointToBall.x*fromPointToBall.x + fromPointToBall.y*fromPointToBall.y)
			Local rDiv:Double = 1.0 / centerDist^(2.0 + goo)
			np.x :+ -goo * ball.size * fromPointToBall.x * rDiv
			np.y :+ -goo * ball.size * fromPointToBall.y * rDiv
		Next
		Local rLen:Double = 1.0 / Sqr(np.x*np.x + np.y*np.y)
		
		Local retval:TPoint = TPoint.Create(np.x*rLen, np.y*rLen)

		
		Return retval
	End Method
	
	Method calcTangent:tpoint(pos:tpoint)
		'return a normalized (magnitude==1) tangent at points "pos"
		Local np:tpoint=Self.calcNormal(pos)
		'###Formula(7)
		Return TPoint.Create( -np.y, np.x )
	End Method
	
	Method stepOnceTowardsBorder:tpoint(pos:tpoint, forceAtPoint:Double) 'Step once towards the border of the metaballs field
		'Local timeStart:Int = MilliSecs()
		Local np:TPoint = calcNormal(pos)
		'### Formula(5)
		'Local stepsize:Double = (minSize/threshold)^(oneOverGoo) - (minSize / forceAtPoint)^(oneOverGoo) + borderStepSize
		Local stepsize:Double =  minOverThresPowGoo - (minSize / forceAtPoint)^(oneOverGoo) + borderStepSize
		'bench1 = bench1 + (MilliSecs() - timeStart)
		Return TPoint.Create( pos.x + np.x*stepsize , pos.y + np.y*stepsize )
	End Method
	
	Method trackTheBorder:tpoint(pos:tpoint)
		'track the border of the metaball field and return new coords
		Local force:Double = 9e7 '9999999.0
		'loop until force is weaker than the desired threshold
		
		'TODO: may loop forever
		Local iters:Int = 0;
		While(force > threshold)
			force = calcForce(pos)
			pos = stepOnceTowardsBorder(pos, force)
			
			?debug
			SetColor 255,255,0
			Plot pos.x, pos.y 'show a little debug output i.e. yellow pixels
			?
			
			iters :+1
			If(iters > 100) 'if it takes to long to find the border/threshold point, break
				Exit
				'Local s# = 5.0
				'DrawOval(pos.x-s,pos.y-s, s, s)
				'Flip
				'DebugStop()
			EndIf
		Wend
		Return pos
	End Method
	
	Method euler:TPoint(pos:TPoint, h:Double)
		Local t1:TPoint = calcTangent(pos)
		Return TPoint.Create( pos.x + h*t1.x, pos.y + h*t1.y )
	End Method
	
	Method rungeKutta2:TPoint(pos:TPoint, h:Double)
	 ' PYTHON: pos + h * Self.calcTangent(pos + Self.calcTangent(pos) * h / 2)
	Local t1:TPoint = calcTangent(pos)
	Local t2:TPoint = calcTangent( TPoint.Create(pos.x + t1.x * h/2, pos.y + t1.y * h/2 ) )
	Return TPoint.Create( pos.x + h*t2.x, pos.y + h*t2.y )
	End Method
	
	Method rungeKutta4:TPoint(pos:TPoint, h:Double)
    't1 = func(pos)
    't2 = func(pos + t1 * h / 2)
    't3 = func(pos + t2 * h / 2)
    't4 = func(pos + t3 * h)
    'Return pos + (h / 6) * (t1 + 2*t2 + 2*t3 + t4)
	Local t1:TPoint = calcTangent(pos)
	Local t2:TPoint = calcTangent( TPoint.Create(pos.x + t1.x * h/2, pos.y + t1.y * h/2 ) )
	Local t3:TPoint = calcTangent( TPoint.Create(pos.x + t2.x * h/2, pos.y + t2.y * h/2 ) )
	Local t4:TPoint = calcTangent( TPoint.Create(pos.x + t3.x * h, pos.y + t3.y * h ) )
	Return TPoint.Create( pos.x + (h/6)*(t1.x + 2*t2.x + 2*t3.x + t4.x), pos.y + (h/6)*(t1.y + 2*t2.y + 2*t3.y + t4.y ) )
	End Method

	Method trackBorderOfAllBalls()
		'it to pos0 and edgePos. The latter will move along the border,
		'pos0 stays at the initial coordinates
		For Local b:TBall = EachIn balls
			Local centerPlusOne:TPoint = TPoint.Create(b.pos.x, b.pos.y-1 ) '(b.pos.x, b.pos.y-1 ) 'b.pos.x+Rnd(-.25,.25), b.pos.y+Rnd(-.25,.25) 
			b.pos0 = trackTheBorder(centerPlusOne)
			b.edgePos = b.pos0
			b.tracking = True
		Next
	End Method
	
	Method drawBalls(stepping:Double)
		'First track the border for all balls and store
		trackBorderOfAllBalls()
		'count how many times we have tracked a single ball. 
		'no need To do it too many times...
		
		
		'For Local ball:tball = EachIn balls
		'	Local siz# = 6
		'	DrawOval(ball.pos.x-siz, ball.pos.y-siz,siz,siz)
		'Next
		glDisable(GL_TEXTURE_2D)
		'glColor3f 1,1,1
		'glBegin(GL_LINES)	
		'glDisable(GL_DEPTH_TEST)
		SetBlend(LIGHTBLEND)
		'glCullFace(GL_NONE)
		
			
			
			
			For Local ball:tball = EachIn balls
				Local loopIndex:Int = 0 
				'glcolor4ub 255,255,255,128
				'glBegin(GL_LINES)
				While(loopIndex < 200)
					loopIndex :+ 1
					
				If(Not ball.tracking) Then Continue  'skip if tracking
				
				'check if we've gone a full circle or hit some other edge tracker
				For Local ob:TBall = EachIn balls
					Local delta:TPoint = TPoint.Create(ob.pos0.x - ball.edgePos.x, ob.pos0.y - ball.edgePos.y)
					Local distanceSq:Double = (delta.x*delta.x) + (delta.y*delta.y)
					
					If( (ob <> ball  Or loopIndex > 3)  And distanceSq < (stepping*stepping) )
						ball.tracking = False
					EndIf
					
				Next 'eof check circle loop
				
				'store the old coordinates for drawing
				Local old_pos:TPoint = ball.edgePos
				
				'walk along the tangent, using chosen differential method
				'ball.edgePos = euler(ball.edgePos, stepping )
				'ball.edgePos = rungeKutta2(ball.edgePos, stepping )
				ball.edgePos = rungeKutta4(ball.edgePos, stepping )
				
				
				'correction step towards the border
				Local forceOnEdge:Double = calcForce(ball.edgePos)
				ball.edgePos = stepOnceTowardsBorder(ball.edgePos, forceOnEdge)
				
				If True
					Local n:TPoint = calcNormal(ball.edgePos)
					SetColor 0,255,0
					DrawLine(ball.edgepos.x, ball. edgepos.y, ball.edgepos.x+n.x*10, ball.edgepos.y+n.y*10) 'draw normal
				EndIf
				
				'glVertex2f(old_pos.x, old_pos.y)
				'glVertex2f(ball.edgePos.x, ball.edgePos.y)
				SetColor 255,255,255
				'SetLineWidth 2
				DrawLine(old_pos.x, old_pos.y, ball.edgePos.x, ball.edgePos.y)
				'SetLineWidth 1
				'Plot (old_pos.x, old_pos.y)
				
				Local tracking:Int = 0 'count how many balls are being tracked.
				For Local ball:TBall = EachIn balls
					If(ball.tracking) Then tracking :+ 1		
				Next
				
				If tracking = 0 Then Exit	
				
				Wend	
				'glEnd()

			
				
			Next 'eof For ball loop
			

			
						
		
		'glEnd()
		'glEnable(GL_TEXTURE_2D)
		
	End Method
	
	Method sampleGrid()	
		Local pointInGrid:TPoint = TPoint.Create(0,0)
		For Local i:Int = 0 Until gridW
		For Local j:Int = 0 Until gridH
			pointInGrid.set( Double(i) * gridSiz, Double(j)*gridSiz ) 
			Local f:Double = calcForce( pointInGrid )
			
			
			If( f > threshold )
				grid[i,j] = True
				'SetColor 255,0,0
			Else
				grid[i,j] = False
				'SetColor 255,255,255	
			EndIf
			
			'Plot i*gridSiz, j*gridSiz
		Next
		Next
	End Method
'http://www.sakri.net/blog/2009/05/28/detecting-edge-pixels-with-marching-squares-algorithm/
'http://users.polytech.unice.fr/~lingrand/MarchingCubes/algo.html
'optimization ideas: start from metaball center:
'http://www.angelcode.com/dev/metaballs/metaballs.asp

'http://www.ia.hiof.no/~borres/cgraph/explain/marching/p-march.html
'http://www.gamedev.net/reference/programming/features/isometa2d/page4.asp
'http://moads.wordpress.com/2008/10/01/marching-squares/
'http://www.tomgibara.com/computer-vision/marching-squares
'http://objectmix.com/graphics/338759-blob-detection-marching-square-lookup-table.html
'Cool! http://www.geometrictools.com/SampleImagics/BinaryOperations/BinaryOperations.html
'http://www.quantum-physics.polytechnique.fr/physix/wiki/index.php/Marching_Square_and_Marching_Cube_algorithms
'good: http://www.thebigblob.com/implementation-of-marching-cubes-in-c-and-opengl/#marching_squares
'raytrace/march http://www.geisswerks.com/ryan/BLOBS/blobs.html

	Method optim()
		For Local i:Int = 0 Until gridW
		For Local j:Int = 0 Until gridH
			grid[i,j] = -1 'not computed
		Next
		Next
		
		Local borderList:TList = New TList
	
		For Local b:TBall = EachIn balls
			Local pos:TPoint = b.pos
			b.isComputed = False
			b.found = False
			Local i:Int = pos.x / gridSiz
			Local j:Int = pos.y / gridSiz
			
			While Not b.found
				If grid[i,j] > 0
					b.isComputed = True
					b.found = True
					Plot pos.x, pos.y
				Else
					'should be computeSquare( position:TPoint )
					
					Local f# = calcForce( TPoint.Create(Float(i)*gridSiz, Float(j)*gridSiz) )
					grid[i,j] = f
					If f > threshold 'if squareCase < 16
						b.found = 1
						'borderList.addLast( TPoint.Create(i*gridSiz,j*gridSiz) ) 
					Else
						pos.y :- 1
					EndIf
				EndIf
			Wend
			
			Local openlist:TList = New TList
			If Not b.isComputed
				'add neighbors to openlist f,pos +TL,TR,BL,BR
				openlist.addLast( TPoint.Create(pos.x,pos.y) )
				openlist.addLast( TPoint.Create(pos.x+gridSiz,pos.y) )
				openlist.addLast( TPoint.Create(pos.x,pos.y+gridSiz) )
				openlist.addLast( TPoint.Create(pos.x+gridSiz,pos.y+gridSiz) )
				
				Local iters:Int = 0
				While Not openlist.isEmpty()
					iters:+1
					If iters > 100 Exit
				
					pos = TPoint( openlist.last() )
					Plot pos.x, pos.y
					openList.removeLast()
					Local f# = calcForce( TPoint.Create(Float(i)*gridSiz, Float(j)*gridSiz) )
					i = pos.x / gridSiz
					j = pos.y / gridSiz
					If i>0 And i<gridW-1 And j>0 And j<gridH-1
					  grid[i,j] = f
					  'borderList.addLast( TPoint.Create(i*gridSiz,j*gridSiz) ) 
					EndIf
					'add neighbours to openlist f,pos 
					openlist.addLast( TPoint.Create(pos.x,pos.y) )
					openlist.addLast( TPoint.Create(pos.x+gridSiz,pos.y) )
					openlist.addLast( TPoint.Create(pos.x,pos.y+gridSiz ) )
					openlist.addLast( TPoint.Create(pos.x+gridSiz,pos.y+gridSiz) )
					
				Wend
			EndIf
			
		Next
		
		Local s# = gridSiz * .5
		Local old_pos:TPoint = Null
		Local startPos:TPoint = Null
		Local mvx#, mvy#
		If borderList
		For Local p:TPoint = EachIn borderList
			If 1
				If old_pos = Null
					old_pos = p
					mvx = p.x
					mvy = p.y
					startPos = p
				Else
					Local lx# = lerp(old_pos.x, p.x, .5)
					Local ly# = lerp(old_pos.y, p.y, .5)
					
					DrawLine(mvx,mvy,lx,ly)
					mvx=lx;
					mvy=ly;
					'Plot lx,ly
					'DrawLine old_pos.x, old_pos.y, p.x, p.y
					old_pos = p
				EndIf
			Else
				DrawRect p.x-s, p.y-s,gridSiz, gridSiz
			EndIf
		Next
		
		'Local lx# = lerp(startPos.x, old_pos.x, .5)
		'Local ly# = lerp(startPos.y, old_pos.y, .5)
		'DrawLine mvx,mvy,lx,ly
		
		EndIf
		
	End Method

	Method marchingSquares()
		sampleGrid()
		
		Local borderList:TList
		borderList = wikiMarching()
		'borderList = sakriDotNetMarching()
		'naivePlot()
		
		Local s# = gridSiz * .5
		Local old_pos:TPoint = Null
		Local startPos:TPoint = Null
		Local mvx#, mvy#
		If borderList
		For Local p:TPoint = EachIn borderList
			If 1
				If old_pos = Null
					old_pos = p
					mvx = p.x
					mvy = p.y
					startPos = p
				Else
					Local lx# = lerp(old_pos.x, p.x, .5)
					Local ly# = lerp(old_pos.y, p.y, .5)
					
					DrawLine(mvx,mvy,lx,ly)
					mvx=lx;
					mvy=ly;
					'Plot lx,ly
					'DrawLine old_pos.x, old_pos.y, p.x, p.y
					old_pos = p
				EndIf
			Else
				DrawRect p.x-s, p.y-s,gridSiz, gridSiz
			EndIf
		Next
		
		Local lx# = lerp(startPos.x, old_pos.x, .5)
		Local ly# = lerp(startPos.y, old_pos.y, .5)
		DrawLine mvx,mvy,lx,ly
		
		EndIf
		
	End Method
	
	Function lerp:Float(x1#,x2#,t#)
		Return x1 + (x2-x1)*t
	End Function
	
	Method getFirstPoint:TPoint()
		Local firstPoint:TPoint = Null
		For Local y:Int = 2 Until gridH-1 Step 1
		For Local x:Int = 2 Until gridW-1 Step 1
			If grid[x,y]
				firstPoint = TPoint.Create( x,y )
				Return firstPoint
			EndIf
		Next
		Next
		Return Null
	End Method
	
	Method sakriDotNetMarching:TList()
		'counterclockwise
		Local first_non_trans:TPoint = getFirstPoint()
		Local points:TList = New TList
		If first_non_trans=Null Return points
		first_non_trans.x :-1; first_non_trans.y :-1 'back up a bit
		Local pos:TPoint = first_non_trans
		points.addLast(first_non_trans)
		Local pointNext:TPoint = Null
		Local i:Int = 0
		Const max_points:Int = 1000
		While(i<MAX_POINTS)
		
			pointNext = getNextEdgePoint(pos)
			If pointNext.x < 0 Or pointNext.x > (gridW-2) Exit
			If pointNext.y < 0 Or pointNext.y > (gridH-2) Exit
			'If pointNext = first Then Exit 'finished when we go full circle
			If Abs(pointNext.x-first_non_trans.x)<0.01 And Abs(pointNext.y-first_non_trans.y)<0.01 Then Exit
			
			points.addLast( TPoint.Create(pointNext.x*gridSiz, pointNext.y*gridSiz) )
			pos = pointNext
			
			i :+ 1
			If i>MAX_POINTS Then DebugStop()
			
		Wend
		Return points
	End Method
	
	Method getNextEdgePoint:TPoint(pos:TPoint)
		Local index:Int = getMarchVector(pos.x, pos.y) 'getMarchVectorSakri(pos.x, pos.y)
		Local p:TPoint = variations_ccw[index]
		If(p=Null)
			DebugLog "marching squares error. Point not found in march[]" 
			DebugStop()
		EndIf
		
		Return TPoint.Create( pos.x + p.x, pos.y + p.y )
	End Method
	
	Method getMarchVectorSakri:Int(x:Int, y:Int)
		Local index:Int = %0000
		If( grid[x+0,y+0] ) 
			index = index | %0001
		EndIf
		
		If( grid[x+1,y] )
			index = index | %0010
		EndIf
		
		If( grid[x,y+1] )
			index = index | %0100
		EndIf
		
		If( grid[x+1,y+1] )
			index = index | %1000
		EndIf
		
		Return index
	End Method
		
	Method wikiMarching:TList() 'http://en.wikipedia.org/wiki/Marching_squares	
		Local borderList:TList = New TList
		
		'trackBorderOfAllBalls()
		
		'trace each shape. todo: stop tracking if allready tracked.
		
		'For Local ball:TBall = EachIn balls
			'start at ball center +
			'Local i:Int = (ball.edgePos.x )/gridSiz
			'Local j:Int = (ball.edgePos.y )/gridSiz
			
			'Local pos:TPoint = TPoint.Create(i,j) 
			Local pos:TPoint = getFirstPoint()
			pos.x :-1; pos.y :-1 'back up 1
			Local loop:TPoint = Null
			Local index:Int = getMarchVector( pos.x,pos.y )'(gridW-2,gridH-2)
			Local march_vector:TPoint = march[index]
			
			Local iter:Int = 0
			Local mvx#, mvy#
			While iter < 1000
				iter:+1
				
				If loop=Null
					loop = pos.clone()
					mvx = pos.x + 1
					mvy = pos.y + 1
				EndIf
				
				pos.x :+ march_vector.x
				pos.y :+ march_vector.y
				
				If(loop)
					borderList.addLast( TPoint.Create(gridSiz*mvx,gridSiz*mvy) ) 
					'DrawLine( Float(mvx)*gridSiz, Float(mvy)*gridSiz,(pos.x+1.0)*gridSiz, (pos.y+1.0)*gridSiz)
					mvx = pos.x+1; mvy = pos.y+1
				EndIf
				Local hitLoop:Int = Abs(pos.x-loop.x) < 0.01 And Abs(pos.y-loop.y) < 0.01
				If(loop And hitLoop) Exit
				
				
				If pos.x < 2 Or pos.x > (gridw-2) Exit
				If pos.y < 2 Or pos.y > (gridh-2) Exit
				
				index = getMarchVector(pos.x, pos.y)
				march_vector = march[index]

			Wend
		
		'Next
		Return borderList
		
	End Method
	
	Method getMarchVector:Int(x:Int, y:Int)
		Local index:Int = %0000
		If( grid[x+1,y+1] ) 
			index = index | %0001
		EndIf
		
		If( grid[x,y+1] )
			index = index | %0010
		EndIf
		
		If( grid[x+1,y] )
			index = index | %0100
		EndIf
		
		If( grid[x,y] )
			index = index | %1000
		EndIf
		
		Return index
	End Method
	
	Method naivePlot:TList() 'bruteforce find borders and plot
		For Local i:Int = 1 Until gridW-1
		For Local j:Int = 1 Until gridH-1
				
			For Local v:Int = -1 To 1 Step 2
			For Local h:Int = -1 To 1 Step 2
			
				Local x1# = Float(i) * gridSiz
				Local y1# = Float(j) * gridSiz
				Local x2# = Float(i+h) * gridSiz
				Local y2# = Float(j+v) * gridSiz
				Local dir1:Int = grid[i,j]
				Local dir2:Int = grid[i+h,j+v]
				If (dir1 + dir2) = 1 'one one border and one inside
					Plot( (x1+x2)*.5, (y1+y2)*.5 ) 
				EndIf
	
			Next
			Next
			
		Next
		Next	
	End Method
End Type
